// Sage Android Client.
// Connect to a Sage server, remotely execute commands and display results.
//
// Copyright (C) 2010, Harald Schilly <harald.schilly@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

/**
 * 
 */

package org.sagemath.android.interacts;

import org.sagemath.android.Calculation;
import org.sagemath.android.CalculationAPI;
import org.sagemath.android.CalculationListener;
import org.sagemath.android.Global;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

/**
 * @author Harald Schilly
 */
public abstract class AbstractInteract {
  
  private static final int MSG_ID = 2;
  
  final protected LayoutInflater layoutInflater;
  
  final Handler handler;
  
  protected View myView;
  
  private CalculationAPI api;
  
  protected Context context;
  
  /**
   * callback to update on result. since it comes from another thread (the IPC
   * mechanism) we have to use the handler to get it on the UI thread. before
   * that, we check if it actually concerns us.
   */
  private CalculationListener calcListener = new CalculationListener.Stub() {
    @Override
    public boolean handleCalculationResult(Calculation c) throws RemoteException {
      // only react to results that were initiated by me
      if (id == c.getId()) {
        handler.sendMessage(handler.obtainMessage(MSG_ID, c));
        return true;
      }
      return false;
    }
  };
  
  /**
   * This is set to the ID of the {@link Calculation}. When we are called back,
   * we expect that this ID matches with the one in the answer.
   */
  private int id = -1;
  
  /**
   * This is the base for all interact like interfaces.
   * It does the basic wireing, adds some thin wrappers around the
   * API and provides two callbacks: {@link AbstractInteract.onResult} and
   * {@link AbstractInteract.onError}.
   * 
   * @param R.layout.[identifyer]
   */
  public AbstractInteract(final int resource) {
    this.api = Global.getCalculationAPI();
    this.context = Global.getContext();
    
    this.layoutInflater = LayoutInflater.from(context);
    
    // Handler are called from non-UI threads to update the UI on the UI thread.
    handler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
        if (MSG_ID == msg.what) {
          onResult((Calculation) msg.obj);
        } else {
          super.handleMessage(msg);
        }
      }
    };
    
    myView = layoutInflater.inflate(resource, null);
    init();
  }
  
  public void addCalcListener() {
    try {
      api.addCalcListener(calcListener);
    } catch (RemoteException re) {
      Log.w(Global.TAG, "Failed to add CalcListener in AbstracInteract", re);
    }
  }
  
  public void removeCalcListener() {
    try {
      api.removeCalcListener(calcListener);
    } catch (RemoteException re) {
      Log.w(Global.TAG, "Failed to remove CalcListener in AbstracInteract", re);
    }
  }
  
  protected void calculate(String code) {
    try {
      id = api.calculate(code);
    } catch (RemoteException ex) {
      onError(ex.toString());
    }
  }
  
  /**
   * Use this method to get the View of this Interact.
   * It basically calls {@link build} to wire up the application.
   * 
   * @return interact view
   */
  public View getView() {
    reset();
    return myView;
  }
  
  /**
   * Called once when object is created. Get UI elements and bind methods to UI
   * events, etc.
   */
  abstract protected void init();
  
  /**
   * Called when {@link Calculation} has finished.
   * 
   * @param c
   */
  abstract protected void onResult(Calculation c);
  
  /**
   * Called when error happened.
   * 
   * @param error
   */
  protected abstract void onError(String error);
  
  /**
   * reset is called when it is shown again (view object stays the same
   * but we have a chance to clean up)
   */
  public abstract void reset();
}
